// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This file implements output formatting for the cobalt config parser.

package config_parser

import (
	"bytes"
	"config"
	"encoding/base64"
	"fmt"
	"github.com/golang/protobuf/proto"
	"strings"
)

type OutputFormatter func(c *config.CobaltConfig) (outputBytes []byte, err error)

// Outputs the serialized proto.
func BinaryOutput(c *config.CobaltConfig) (outputBytes []byte, err error) {
	return proto.Marshal(c)
}

// Outputs the serialized proto base64 encoded.
func Base64Output(c *config.CobaltConfig) (outputBytes []byte, err error) {
	configBytes, err := BinaryOutput(c)
	if err != nil {
		return outputBytes, err
	}
	encoder := base64.StdEncoding
	outLen := encoder.EncodedLen(len(configBytes))

	outputBytes = make([]byte, outLen, outLen)
	encoder.Encode(outputBytes, configBytes)
	return outputBytes, nil
}

// writeIdConstants prints out a list of constants to be used in testing. It
// uses the Name attribute of each Metric, Report, and Encoding to construct the
// constants.
//
// For a metric named "SingleString" the constant would be kSingleStringMetricId
// For a report named "Test" the constant would be kTestReportId
// For an encoding named "Forculus" the canstant would be kForculusEncodingId
func writeIdConstants(out *bytes.Buffer, constType string, entries map[string]uint32) {
	if len(entries) == 0 {
		return
	}
	out.WriteString(fmt.Sprintf("// %s ID Constants\n", constType))
	for name, id := range entries {
		name = strings.Join(strings.Split(name, " "), "")
		out.WriteString(fmt.Sprintf("const uint32_t k%s%sId = %d;\n", name, constType, id))
	}
	out.WriteString("\n")
}

// Returns an output formatter that will output the contents of a C++ header
// file that contains a variable declaration for a string literal that contains
// the base64-encoding of the serialized proto.
//
// varName will be the name of the variable containing the base64-encoded serialized proto.
// namespace is a list of nested namespaces inside of which the variable will be defined.
// configLocation is the location of the YAML that was parsed.
func CppOutputFactory(varName string, namespace []string, configLocation string) OutputFormatter {
	return func(c *config.CobaltConfig) (outputBytes []byte, err error) {
		b64Bytes, err := Base64Output(c)
		if err != nil {
			return outputBytes, err
		}

		out := new(bytes.Buffer)
		out.WriteString("// Copyright 2018 The Fuchsia Authors. All rights reserved.\n")
		out.WriteString("// Use of this source code is governed by a BSD-style license that can be\n")
		out.WriteString("// found in the LICENSE file.\n\n")
		out.WriteString("#pragma once\n\n")
		out.WriteString("// This file was generated by Cobalt's Config Parser based on the\n")
		out.WriteString("// configuration YAML in the following location:\n")
		out.WriteString(fmt.Sprintf("// %s\n", configLocation))
		out.WriteString("// Edit the YAML at that location to make changes.\n\n")

		for _, name := range namespace {
			out.WriteString("namespace ")
			out.WriteString(name)
			out.WriteString(" {\n")
		}

		metrics := make(map[string]uint32)
		for _, metric := range c.MetricConfigs {
			if metric.Name != "" {
				if _, ok := metrics[metric.Name]; ok {
					return outputBytes, fmt.Errorf("Duplicate metric name found %v", metric.Name)
				}
				metrics[metric.Name] = metric.Id
			}
		}
		// Write out the 'Metric' constants (e.g. kTestMetricId)
		writeIdConstants(out, "Metric", metrics)

		reports := make(map[string]uint32)
		for _, report := range c.ReportConfigs {
			if report.Name != "" {
				if _, ok := reports[report.Name]; ok {
					return outputBytes, fmt.Errorf("Duplicate report name found %v", report.Name)
				}
				reports[report.Name] = report.Id
			}
		}
		// Write out the 'Report' constants (e.g. kTestReportId)
		writeIdConstants(out, "Report", reports)

		encodings := make(map[string]uint32)
		for _, encoding := range c.EncodingConfigs {
			if encoding.Name != "" {
				if _, ok := encodings[encoding.Name]; ok {
					return outputBytes, fmt.Errorf("Duplicate encoding name found %v", encoding.Name)
				}
				encodings[encoding.Name] = encoding.Id
			}
		}
		// Write out the 'Encoding' constants (e.g. kTestEncodingId)
		writeIdConstants(out, "Encoding", encodings)

		out.WriteString("// The base64 encoding of the bytes of a serialized CobaltConfig proto message.\n")
		out.WriteString("const char ")
		out.WriteString(varName)
		out.WriteString("[] = \"")
		out.Write(b64Bytes)
		out.WriteString("\";\n")

		for _, name := range namespace {
			out.WriteString("} // ")
			out.WriteString(name)
			out.WriteString("\n")
		}
		return out.Bytes(), nil
	}
}
